package com.bving.crawler.service.impl;

import com.bving.crawler.pojo.model.CarTestModel;
import com.bving.crawler.service.ApiService;
import com.bving.crawler.service.ICarTestService;
import com.bving.crawler.util.TitleFilter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;

/**
 * @description
 *
 */
@Service
public class ApiServiceImpl implements ApiService {

    @Autowired
    private PoolingHttpClientConnectionManager cm;
    @Autowired
    private TitleFilter titleFilter;
    @Autowired
    private ICarTestService carTestService;

    /**
     * 获取请求参数对象
     * @return
     */
    private RequestConfig getConfig(){
        return RequestConfig.custom()
                    .setConnectTimeout(1000)            //设置创建连接的 超时时间
                    .setConnectionRequestTimeout(500)   //设置获取连接的超时时间
                    .setSocketTimeout(10000)            //设置连接的超时时间
                    .build();
    }

    @Override
    public String getHtml(String url) {
        //1.获取HttpClient对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();

        //2.声明HttpGet请求对象
        HttpGet httpGet = new HttpGet(url);

        //3.设置用户代理信息
        httpGet.setHeader("User-Agent", "");

        //4.设置请求参数RequestConfig
        httpGet.setConfig(getConfig());

        //5.使用HttpClient发起请求，返回response
        String html="";
        try(CloseableHttpResponse response = httpClient.execute(httpGet)) {
            //6.解析response返回数据
            html = Optional.ofNullable(response).map(resp -> {
                if (resp.getStatusLine().getStatusCode() == 200) {
                    return resp.getEntity();
                } else {
                    return null;
                }
            }).map(entity -> {
                try {
                    return EntityUtils.toString(entity, "UTF-8");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return "";
            }).orElse("");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return html;
    }

    @Override
    public String getImage(String url) {
        //1.获取HttpClient对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();

        //2.声明HttpGet请求对象
        HttpGet httpGet = new HttpGet(url);

        //3.设置用户代理信息
        httpGet.setHeader("User-Agent", "");

        //4.设置请求参数RequestConfig
        httpGet.setConfig(getConfig());

        //5.使用HttpClient发起请求，返回response
        String imageName="";
        try(CloseableHttpResponse response = httpClient.execute(httpGet)) {
            //6.解析response返回数据
            imageName = Optional.ofNullable(response).map(resp -> {
                if (resp.getStatusLine().getStatusCode() == 200) {
                    return resp.getEntity();
                } else {
                    return null;
                }
            }).map(entity -> {
                //获取文件类型(image/gif、image/png等)
                String extName = "." + entity.getContentType().getValue().split("/")[1];
                //使用uuid生成图片名称
                String imgName = UUID.randomUUID().toString()+extName;
                //输出的文件地址
                String path="D:/images/";
                try {
                    //声明输出的文件
                    OutputStream outputStream = new FileOutputStream(new File(path+imgName));
                    //使用响应体输出文件
                    entity.writeTo(outputStream);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return imgName;
            }).orElse("");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return imageName;
    }

    @Override
    public String crawlerHtml(String html) {
        String result = "error";

        try {
            Document dom = Jsoup.parse(html);
            Elements divs = dom.select("#bestautocontent div.uibox");
            for (Element div : divs) {
                //去重过滤,重复的数据就不需要再处理了
                String title = div.select("div.uibox-title").first().text();
                if (titleFilter.contains(title)){
                    //如果数据存在,表示重复,进行下次遍历
                    continue;
                }
                //解析页面，获取评测数据对象
                CarTestModel carTestModel = getCarTestModel(div);

                //解析页面,下载车辆评测图片
                String carImage = getCarImage(div);
                //把汽车数据存到数据库
                carTestModel.setImage(carImage);
                carTestService.save(carTestModel);
                result = "ok";
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }

    /**
     * 解析传递进来的元素，封装为汽车评测对象
     * @param div
     * @return
     */
    private CarTestModel getCarTestModel(Element div){
        CarTestModel carTestModel = new CarTestModel(null,
                div.select("div.uibox-title").first().text(),
                changeStr2Num(div.select(".tabbox1 dd:nth-child(2) div.dd-div2").first().text()),
                changeStr2Num(div.select(".tabbox1 dd:nth-child(3) div.dd-div2").first().text()),
                changeStr2Num(div.select(".tabbox1 dd:nth-child(4) div.dd-div2").first().text()),
                div.select(".tabbox2 dd:nth-child(2) > div.dd-div1").first().text(),
                div.select(".tabbox2 dd:nth-child(2) > div.dd-div3").first().text(),
                div.select(".tabbox2 dd:nth-child(3) > div.dd-div1").first().text(),
                div.select(".tabbox2 dd:nth-child(3) > div.dd-div3").first().text(),
                div.select(".tabbox2 dd:nth-child(4) > div.dd-div1").first().text(),
                div.select(".tabbox2 dd:nth-child(4) > div.dd-div3").first().text(),
                null,
                new Date(),
                new Date());

        return carTestModel;
    }

    /**
     * 解析传递进来的元素，下载评测图片
     * @param div
     * @return 下载后的图片名字
     */
    private String getCarImage(Element div){
        //声明存放图片的集合
        List<String> images = new ArrayList<>();
        //获取图的url地址
        Elements page = div.select("ul.piclist02 li");
        //遍历评测图片的元素
        for (Element element : page) {
            //获取评测图片的展示地址
            String imagePage = "https:" + element.select("a").attr("href");
            //获取图片展示页面
            String html = getHtml(imagePage);
            //解析图片展示页面
            Document dom = Jsoup.parse(html);
            //获取图片地址
            String imageUrl ="https:" + dom.getElementById("img").attr("src");
            //下载图片并返回图片名称
            String imageName = getImage(imageUrl);
            images.add(imageName);
        }

        return ArrayUtils.toString(images);
    }

    /**
     * 把字符串最后一个字符去掉,转为数字,再乘以1000
     * @param str
     */
    private long changeStr2Num(String str){
        String substring = StringUtils.substring(str, 0, str.length() - 1);
        substring = isNumeric(substring.trim())?substring.trim():"0";
        //把字符串转为小数,必须用Number接收,否则会有精度丢失的问题
        Number num = substring.isEmpty() ? 0 : Double.parseDouble(substring.trim())*1000;
        return num.longValue();
    }

    /**
     * 判断字符串是否为数字
     * @param str
     * @return
     */
    public boolean isNumeric(String str) {
        //看它是否能转化为一个数
        try {
            double db=Double.parseDouble(str);
        } catch (Exception e) {
            return false;
        }
        return true;
    }
}